package scatter.common.rest.interceptor;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import scatter.common.LoginUser;
import scatter.common.rest.monitor.MonitorTool;
import scatter.common.rest.notify.NotifyParam;
import scatter.common.rest.notify.NotifyTool;
import scatter.common.rest.security.LoginUserTool;
import scatter.common.rest.exception.IErrorLogListener;
import scatter.common.rest.tools.ErrorLogCollectorTool;
import scatter.common.rest.tools.InterfaceTool;
import scatter.common.rest.tools.ThreadContextTool;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;

/**
 * 全局拦截
 * 适用于基于 spring 的 controller 拦截
 * Created by yangwei
 * Created at 2019/9/27 15:46
 */
@Slf4j
@Component
public class GlobalInterceptor implements AsyncHandlerInterceptor, InterfaceTool {

    // 计时器key
    public static final String TIME_START_KEY = "timeStartKey";
    // 记录是否有异常key
    public static final String HAS_EXCEPTION_KEY = "hasExceptionKey";
    public static final String EXCEPTION_KEY = "exceptionKey";

    @Lazy
    @Autowired(required = false)
    private IErrorLogListener iErrorLogListener;
    @Value("${scatter.notify.handler.threshold:500}")
    private long handlerNotifyThreshold = 500;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截器开始");

        Object principal = Optional.ofNullable(SecurityContextHolder.getContext())
                .map(securityContext -> securityContext.getAuthentication())
                .map(authentication -> authentication.getPrincipal()).orElse(null)
                ;
        String userInfo = "";
        if (principal != null && !(principal instanceof String)) {
            if (principal instanceof LoginUser) {
                LoginUserTool.saveToSession((LoginUser) principal, request);
            }
            userInfo = toJsonStr(principal);
        }
        if ((principal instanceof String)) {
            userInfo = ((String) principal);
        }

        long start = System.currentTimeMillis();
        ThreadContextTool.put(TIME_START_KEY,start);

        log.info("当前登录用户: loginUser={}",userInfo);

        if(handler instanceof HandlerMethod){
            Object annotationValue = AnnotationUtil.getAnnotationValue(((HandlerMethod) handler).getMethod(), ApiOperation.class);
            log.info("请求接口名称: apiName={},method={}",
                     annotationValue,((HandlerMethod) handler).toString());
            ErrorLogCollectorTool.collect("apiName",annotationValue);
        }
        return true;
    }

    /**
     * 全局拦截完成处理
     * 这里一般不会有异常传过来，因为所有的异常都被grape.common.rest.advice.GlobalExceptionAdvice拦截处理
     * 如果这里有异常说明全局拦截异常处理没有关注该异常
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        long start = (long) ThreadContextTool.get(TIME_START_KEY);
        long duration = System.currentTimeMillis() - start;

        Boolean hasExceptionInGlobalExceptionAdvice = ((Boolean) ThreadContextTool.get(HAS_EXCEPTION_KEY));
        boolean hasError = false;
        if (ex != null || (hasExceptionInGlobalExceptionAdvice != null && hasExceptionInGlobalExceptionAdvice)) {
            hasError = true;
        }
        if (duration > handlerNotifyThreshold) {
            log.warn("请求处理时间过长：应最少控制在{}ms以内，实际{}ms",handlerNotifyThreshold,duration);
            NotifyParam notifyParam = NotifyParam.system();
            notifyParam.setContentType("handler.duration");
            notifyParam.setTitle("handler.duration 执行时间超过阈值");
            notifyParam.setContent(StrUtil.format("handler.duration 执行时间耗时{}ms超过阈值{}ms,url={}",duration,handlerNotifyThreshold,request.getRequestURI()));
            notifyParam.setSuggest("您可以通过修改配置 scatter.handler.notify.threshold 来改变阈值");
            NotifyTool.notify(notifyParam);
        }
        String msg = "拦截器结束: duration={}ms";
        if (hasError) {
            log.error(msg,duration);
            if (iErrorLogListener != null) {
                iErrorLogListener.onError(request,response,handler,ex != null ? ex : ((Exception) ThreadContextTool.get(EXCEPTION_KEY)));
            }
            NotifyParam notifyParam = NotifyParam.system();
            notifyParam.setTitle("global.interceptor 异常")
                    .setContentType("lobal.interceptor")
                    .setContent(ExceptionUtil.stacktraceToString(ex != null ? ex : ((Exception) ThreadContextTool.get(EXCEPTION_KEY))));
            NotifyTool.notify(notifyParam);
        } else {
            log.info(msg,duration);
        }

        // 移除所有线程变量
        ThreadContextTool.remove();
    }

}
